﻿#region using statements

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNAGameEngine.Utility;

#endregion

namespace XNAGameEngine.GameObject
{
    /// <summary>
    /// <para>Base class for an object in the game engine.</para>
    /// <para>All objects must inherit this class in order for it to be registered in the game engine.</para>
    /// <para>Be sure to call the base constructor in your implementation whenever you inherit this class.</para>
    /// </summary>
    public abstract class GameObject2D
    {
        #region events

        private event ManagerGameObject2D.GameObjectEventHandler
            ThrowGameObject2DDestroyedEvent;

        #endregion

        #region local fields

        /// <summary>
        /// reference to the game engine
        /// </summary>
        private static Game _game;

        /// <summary>
        /// Reference to the game engine
        /// </summary>
        private static IManagerGameObject2D _gameObjectsManager;

        /// <summary>
        /// <para>The value of the unique integer identifier for this object.</para>
        /// <para>This value is set whenever this object is created.</para>
        /// <para>You cannot (and should not need to) change this value.</para>
        /// </summary>
        public readonly int _objectId;

        /// <summary>
        /// <para>The origin of the object</para>
        /// <para>Default is (0,0) (top left corner)</para>
        /// </summary>
        public Vector2 _origin;

        /// <summary>
        /// <para>Position of the object on screen</para>
        /// <para>Default is (0,0) (top left corner)</para>
        /// </summary>
        public Vector2 _position;

        /// <summary>
        /// <para>Positional velocity of the object</para>
        /// <para>Object automatically gets this value added during each call of update</para>
        /// <para>Default is (0,0)</para>
        /// </summary>
        public Vector2 _posVelocity;

        /// <summary>
        /// <para>Positional acceleration of the object</para>
        /// <para>Default is (0,0)</para>
        /// </summary>
        public Vector2 _posAcceleration;

        /// <summary>
        /// This is the mass of the object.  It's default value will be 1.
        /// </summary>
        public float _mass;

        /// <summary>
        /// <para>The ange value of the object, in degrees, to rotate around the origin</para>
        /// <para>Default is 0</para>
        /// </summary>
        public double _angle;

        /// <summary>
        /// <para>Angular velocity of the object</para>
        /// <para>(how many degrees it rotates per second)</para>
        /// <para>Default is 0</para>
        /// </summary>
        public double _angVelocity;

        /// <summary>
        /// <para>Rotational acceleration of the object</para>
        /// <para>Default is 0</para>
        /// </summary>
        public double _angAcceleration;

        /// <summary>
        /// <para>Scale the object size</para>
        /// <para>Can be scaled in the X and Y directions</para>
        /// <para>This scales both the image and the bounding box</para>
        /// <para>Changing the x value to 2.5 will make it scale in the by 2.5x its size</para>
        /// <para>Default is 1</para>
        /// </summary>
        public Vector2 _scale;

        /// <summary>
        /// <para>Linear rate of change of the object size</para>
        /// <para>Default is 0,0</para>
        /// </summary>
        public Vector2 _scaVelocity;

        /// <summary>
        /// <para>Exponential rate of the object size</para>
        /// <para>Default is 0,0</para>
        /// </summary>
        public Vector2 _scaAcceleration;

        /// <summary>
        /// <para>The sprite effects to use when rendering</para>
        /// <para>FlipHorizontal, FlipVertical, None</para>
        /// <para>Default is None</para>
        /// </summary>
        public SpriteEffects _spriteEffects;

        /// <summary>
        /// <para>The image graphic representing this object</para>
        /// </summary>
        public Texture2D _texture;

        /// <summary>
        /// <para>The image offset from the object origin</para>
        /// </para>Default is 0</para>
        /// </summary>
        public Vector2 _textureOffset;

        /// <summary>
        /// <para>Color tint applied on top of the image</para>
        /// <para>Default is white</para>
        /// </summary>
        public Color _textureTint;

        /// <summary>
        /// <para>The Z depth of the object</para>
        /// <para>This determines which objects are drawn on top of others</para>
        /// <para>Value must be between 0 and 1</para>
        /// <para>Default is 0</para>
        /// </summary>
        public float _zDepth;

        #endregion

        #region properties

        /// <summary>
        /// Gets a reference to the GameEngine
        /// </summary>
        public Game Game
        {
            get { return _game; }
        }

        /// <summary>
        /// Gets the actual width of the texture, takes into account scaling
        /// </summary>
        public int ActualWidth { get { return (int)(_texture.Width * _scale.X); } }

        /// <summary>
        /// Gets the actual width of the texture, takes into account scaling
        /// </summary>
        public int ActualHeight { get { return (int)(_texture.Height * _scale.Y); } }
        
        /// <summary>
        /// Gets a reference to the GameObjectsManager
        /// </summary>
        public IManagerGameObject2D _GameObjectManager
        {
            get { return _gameObjectsManager; }
        }


        #endregion

        #region constructors

        /// <summary>
        /// Default constructor sets the value of the reference to the game engine
        /// </summary>
        /// <param name="gameEngine">a reference to the game engine</param>
        /// <param name="objectId">the unique object identifier number for this object</param>
        public GameObject2D(int objectId)
        {
            if (_game == null || _gameObjectsManager == null)
                throw new SystemException(
                    "GameObject2D references are not set. This is probably bad and it should not be possible."
                    +
                    "Make sure these values are set before trying to create a game object.");

            _objectId = objectId;
            ThrowGameObject2DDestroyedEvent +=
                ((ManagerGameObject2D)_gameObjectsManager).OnObjectDestroyed;
            _origin = new Vector2();
            _position = new Vector2();
            _posVelocity = new Vector2();
            _posAcceleration = new Vector2();
            _mass = 1;
            _angle = 0;
            _angVelocity = 0;
            _angAcceleration = 0;
            _scale = new Vector2(1, 1);
            _spriteEffects = SpriteEffects.None;
            _textureOffset = new Vector2();
            _textureTint = Color.White;
            _zDepth = 0;
        }

        #endregion

        #region local methods

        /// <summary>
        /// Sets the references to the game engine and game objects manager inside of the gameobjects
        /// </summary>
        /// <param name="gameEngine">the GameEngine</param>
        /// <param name="gameObjectsManager">the GameObjectsManager2D managing objects within the game Engine</param>
        public static void SetReferences(
            Game game, IManagerGameObject2D gameObjectsManager)
        {
            _game = game;
            _gameObjectsManager = gameObjectsManager;
        }

        /// <summary>
        /// Initialize the GameObject
        /// </summary>
        public virtual void Initialize()
        {
        }

        /// <summary>
        /// Cleanup the GameObject and remove from the ManagerGameObject
        /// </summary>
        public virtual void Destroy()
        {
            ThrowGameObject2DDestroyedEvent(_objectId);
        }

        /// <summary>
        /// Update the GameObject
        /// </summary>
        /// <param name="gameTime">the game time</param>
        public virtual void Update(GameTime gameTime)
        {
            _scaAcceleration += _scaVelocity;
            _scale += _scaVelocity;
            _posVelocity += MathToolbox.MultVector2ByScalar(
                _posAcceleration, gameTime.ElapsedGameTime.Milliseconds);
            _position += MathToolbox.MultVector2ByScalar(
                _posVelocity, gameTime.ElapsedGameTime.Milliseconds);
            _angVelocity += _angAcceleration
                            * gameTime.ElapsedGameTime.Milliseconds;
            _angle += _angVelocity * gameTime.ElapsedGameTime.Milliseconds;
        }

        /// <summary>
        /// Draw the GameObject
        /// </summary>
        /// <param name="gameTime">the game time</param>
        public virtual void Draw(GameTime gameTime)
        {
            if (_texture == null)
                return;

            GameEngine.SpriteBatch.Draw(
                _texture, _position, null, _textureTint,
                (float)_angle, _origin, _scale, _spriteEffects, _zDepth);
        }

        #endregion
    }
}